/*
 * Copyright (c) 2025 Li Auto Inc. and its affiliates
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "UDSDgramChannelResource.h"
#include "UDSDgramTransportInterface.h"
#include <asio.hpp>
#include <string>
#include <transport/ReceiverResource.h>

using namespace vbs::transport;

namespace vbs {
namespace transport {

UDSDgramChannelResource::UDSDgramChannelResource(UDSDgramTransportInterface* transport,
                                                 asio::local::datagram_protocol::socket& socket, uint32_t maxMsgSize,
                                                 const vbsutil::xmlparser::Locator_t& locator,
                                                 std::shared_ptr<ReceiverResource> receiver)
    : ChannelResource(maxMsgSize),
      message_receiver_(std::move(receiver)),
      socket_(std::move(socket)),
      transport_(transport) {
    std::stringstream ss;
    std::string path = socket_.local_endpoint().path();
    std::string display_name = path.length() >= 8 ? path.substr(path.length() - 8) : path;

    ss << "R" << std::hex << std::setw(8) << std::setfill('0') << display_name;
    std::string thread_name(ss.str());
    (void)transport_;
    logInfo(TRANSPORT, "UDSChannelResource CREATE locator" << locator);
    thread(std::thread(&UDSDgramChannelResource::perform_listen_operation, this, locator, thread_name));
}

UDSDgramChannelResource::~UDSDgramChannelResource() {
    message_receiver_ = nullptr;
    try {
        std::string remove_path = socket_.local_endpoint().path();
        socket()->close();
        if (std::remove(remove_path.c_str()) == 0) {
            logInfo(TRANSPORT, "Delete the receive resource, path: " << remove_path);
        } else {
            logError(TRANSPORT, "Failed to delete the receive resource!!! path: " << remove_path);
        }
    } catch (const asio::system_error& e) {
        logError(TRANSPORT, "Exception in destructor: " << e.what());
    }
}

static inline std::string remove_vbs_prefix_for_path(const std::string& filename) {
    const std::string prefix = "/dev/socket/vbs/vbs_";
    if (filename.compare(0, prefix.size(), prefix) == 0) {
        // remove /dev/socket/vbs/vbs_
        return filename.substr(prefix.size());
    } else {
        return filename;
    }
}

void UDSDgramChannelResource::perform_listen_operation(vbsutil::xmlparser::Locator_t input_locator,
                                                       const std::string& thread_name) {
    Locator remote_locator;
    logDebug(TRANSPORT, "perform_listen_operation  start listening  ... " << input_locator);
    (void)thread_name;

    int ret = pthread_setname_np(pthread_self(), thread_name.c_str());
    if (ret != 0) {
        logWarning(TRANSPORT, "Failed to set thread name: " << ret << " " << thread_name);
    }
    // socket()->non_blocking(true);
    // input_locator;
    while (alive()) {
        unsigned char* buffer = message_buffer();
        uint32_t length = 0;
        asio::local::datagram_protocol::endpoint remote_endpoint;
        if (!Receive(buffer, buffer_size(), length, remote_endpoint)) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            continue;
        }
        if (message_receiver() != nullptr) {
            std::string path =
                remote_endpoint.path().substr(0, remote_endpoint.path().size() - UDSDgramMagicWord.size());
            std::memset(remote_locator.address, 0, LOCATOR_ADDRESS_MAX_LEN);
            remote_locator.kind = LOCATOR_KIND_UDS;
            path = remove_vbs_prefix_for_path(path);
            std::strncpy(reinterpret_cast<char*>(remote_locator.address), path.c_str(),
                         sizeof(remote_locator.address) - 1);
            remote_locator.address[sizeof(remote_locator.address) - 1] = '\0';
            message_receiver()->OnDataReceived(buffer, length, input_locator, remote_locator);
        } else if (alive()) {
            logInfo(TRANSPORT, "Received Message, but no receiver attached");
        }
    }
    clear_message_receiver();
}

bool UDSDgramChannelResource::Receive(unsigned char* receive_buffer, uint32_t receive_buffer_capacity,
                                      uint32_t& receive_buffer_size,
                                      asio::local::datagram_protocol::endpoint& remote_endpoint) {
    try {
        size_t bytes = socket()->receive_from(asio::buffer(receive_buffer, receive_buffer_capacity), remote_endpoint);
        receive_buffer_size = static_cast<uint32_t>(bytes);
        if (receive_buffer_size > 0) {
            if ((receive_buffer_size == 13) && (memcmp(receive_buffer, "EPRORTPSCLOSE", 13) == 0)) {
                return false;
            }
        }
        return (receive_buffer_size > 0);
    } catch (const std::system_error& e) {
        if (e.code() == std::make_error_code(std::errc::resource_unavailable_try_again)) {
        } else {
            // logDebug(TRANSPORT,
            //             "System Error receiving data: " << e.what() << " - " << message_receiver() << "(" << this << ")");
        }
        return false;
    } catch (const std::exception& e) {
        (void)e;
        logInfo(TRANSPORT, "Error receiving data: " << e.what() << " - " << message_receiver() << "(" << this << ")");
        return false;
    }
}

void UDSDgramChannelResource::release() {
    // Cancel all asynchronous operations associated with the socket.
    socket()->cancel();
    asio::error_code ec;
    (void)socket()->shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec);
}

}  // namespace transport
}  // namespace vbs
